﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="WeakCollection.cs" company="JDML Consulting Ltd.">
//   Copyright 2011 Photon Project - http://photon.codeplex.com
// </copyright>
// <summary>
//   Defines the WeakCollection type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Photon.Collections
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using ComponentModel;

    public class WeakCollection<T> : ICollection<T> where T : class
    {
        #region Fields

        private readonly List<WeakReference> _items;
        private int _updateCount;

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="WeakCollection&lt;T&gt;"/> class.
        /// </summary>
        public WeakCollection()
        {
            _items = new List<WeakReference>();
        }

        #region ICollection<T> Members

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="IEnumerator{T}"/> object that can be used to iterate through the collection.
        /// </returns>
        public IEnumerator<T> GetEnumerator()
        {
            return _items.Select(item => item.Target as T).Where(target => target != null).GetEnumerator();
        }

        /// <summary>
        /// Returns an enumerator that iterates through a collection.
        /// </summary>
        /// <returns>
        /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
        /// </returns>
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        /// <summary>
        /// Adds the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        public void Add(T item)
        {
            #region Validate Arguments

            Guard.ArgumentNull("item", item);

            #endregion

            if (Count == Capacity)
            {
                Compact();
            }

            _items.Add(new WeakReference(item));
        }

        /// <summary>
        /// Removes the specified item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns><c>true</c> if the item was removed; otherwise <c>false</c>.</returns>
        public bool Remove(T item)
        {
            if (ReferenceEquals(item, null))
            {
                return false;
            }

            var index = _items.FindIndex(x => EqualityComparer<T>.Default.Equals((T)x.Target, item));
            if (index != -1)
            {
                _items.RemoveAt(index);
                if (Count * 2.5 < Capacity)
                {
                    Compact();
                }
            }
            
            return index != -1;
        }

        /// <summary>
        /// Clears this instance.
        /// </summary>
        public void Clear()
        {
            _items.Clear();
            Compact();
        }

        /// <summary>
        /// Begins an update operation on the collection.
        /// </summary>
        /// <returns>An <see cref="IDisposable"/> that can be used to scope the update 
        /// operation.</returns>
        /// <remarks>
        /// When within the scope of an update operation the collection will not automatically 
        /// compact. 
        /// </remarks>
        public IDisposable BeginUpdateOperation()
        {
            return new UpdateOperation(this);
        }

        /// <summary>
        /// Determines whether the collection contains the specified  item.
        /// </summary>
        /// <param name="item">The item.</param>
        /// <returns><c>true</c> if the collection contains the specified item; otherwise, <c>false</c>.
        /// </returns>
        public bool Contains(T item)
        {
            return _items.Any(x => ReferenceEquals(x.Target, item));
        }

        /// <summary>
        /// Copies the elements of the <see cref="ICollection{T}"/> to an Array, starting at a particular Array index. 
        /// </summary>
        /// <param name="array">The one-dimensional Array that is the destination of the elements copied from ICollection. The Array must have zero-based indexing.</param>
        /// <param name="arrayIndex">The zero based index in <paramref name="array"/> as which copying begins.</param>
        /// <remarks>
        /// Consumers rely on <see cref="Count"/> to determine how much space is required in the 
        /// <paramref name="array"/> to contain all of the items. Because the <see cref="CopyTo"/> method 
        /// does not indicate how many items have actually been copied the <see cref="WeakCollection{T}"/> 
        /// will always fill up <see cref="Count"/> entries in the <paramref name="array"/> with strong 
        /// references at the start of the array padded at the end with null references for any collected 
        /// items.
        /// </remarks>
        public void CopyTo(T[] array, int arrayIndex)
        {
            #region Validate Arguments

            CollectionGuard.CopyToInvalidArguments(array, arrayIndex, Count);

            #endregion

            var index = arrayIndex;
            foreach (var target in this)
            {
                array[index++] = target;
            }

            Array.Clear(array, index, Count - (index - arrayIndex));
        }
        
        /// <summary>
        /// Gets the count.
        /// </summary>
        /// <value>The count.</value>
        /// <remarks>
        /// For the <see cref="WeakCollection{T}"/> the <see cref="Count"/> property represents 
        /// the total number of weak references managed by the collection. It is not guaranteed 
        /// to match the number of items returned when enumerating the collection, however, it 
        /// is guaranteed to never be less than the number of items returned.
        /// </remarks>
        public int Count
        {
            get { return _items.Count; }
        }

        /// <summary>
        /// Gets or sets the capacity.
        /// </summary>
        /// <value>The capacity.</value>
        public int Capacity
        {
            get { return _items.Capacity; }
            set { _items.Capacity = value; }
        }

        /// <summary>
        /// Gets a value indicating whether this instance is read only.
        /// </summary>
        /// <value><c>true</c> if this instance is read only; otherwise, <c>false</c>.</value>
        public bool IsReadOnly
        {
            get { return false; }
        }

        #endregion

        /// <summary>
        /// Compacts this list.
        /// </summary>
        public void Compact()
        {
            if (_updateCount > 0)
            {
                return;
            }

            // remove all dead references
            _items.RemoveAll(IsDeadReference);

            // trim excess (as capacity tends to double, let's not shrink unless we are outside of that bounds)
            var capacityThreshold = Count * 2;
            if (Capacity > capacityThreshold)
            {
                Capacity = Count != 0 ? Math.Max(4, capacityThreshold) : 0;
            }
        }

        /// <summary>
        /// Determines whether the specified weak reference is dead.
        /// </summary>
        /// <param name="reference">The weak reference.</param>
        /// <returns><c>true</c> if the reference is dead; otherwise, <c>false</c>.</returns>
        private static bool IsDeadReference(WeakReference reference)
        {
            return !reference.IsAlive;
        }

        /// <summary>
        /// Converts the collection to a list, removing any dead/null references.
        /// </summary>
        /// <remarks>
        /// The default LINQ extension method <see cref="Enumerable.ToList{T}(IEnumerable{T})"/>
        /// utilizes the <see cref="Count"/> and <see cref="CopyTo "/> methods to create a 
        /// new list. The <see cref="WeakCollection{T}"/> defines it own version of 
        /// <see cref="ToList"/> to ensure that the items are enumerated fully and any 
        /// collected/null references are filtered out of the results.
        /// </remarks>
        /// <returns>The collection represented as a list.</returns>
        public List<T> ToList()
        {
            var list = new List<T>(Math.Max(4, _items.Capacity / 2));
            
            // Do not let resharper refactor this into an AddRange as it will not filter collected references.
            foreach (var item in this)
            {
                list.Add(item);
            }

            return list;
        }

        /// <summary>
        /// Increments the update count.
        /// </summary>
        private void IncrementUpdateCount()
        {
            if (_updateCount == Int32.MaxValue)
            {
                throw new OverflowException();
            }

            _updateCount++;
        }

        /// <summary>
        /// Decrements the update count.
        /// </summary>
        private void DecrementUpdateCount()
        {
            _updateCount--;
        }

        /// <summary>
        /// Used to track update operations on the <see cref="WeakCollection{T}"/>
        /// </summary>
        public class UpdateOperation : IDisposable
        {
            #region Fields

            private WeakCollection<T> _owner;

            #endregion

            /// <summary>
            /// Initializes a new instance of the <see cref="WeakCollection&lt;T&gt;.UpdateOperation"/> class.
            /// </summary>
            /// <param name="owner">The owner.</param>
            public UpdateOperation(WeakCollection<T> owner)
            {
                _owner = owner;
                _owner.IncrementUpdateCount();
            }

            /// <summary>
            /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
            /// </summary>
            public void Dispose()
            {
                if (_owner != null)
                {
                    _owner.DecrementUpdateCount();
                    _owner = null;
                }
            }
        }
    }
}